package org.example.javatest.annotation;

import org.junit.jupiter.api.Test;

import java.lang.annotation.Annotation;

import static org.junit.jupiter.api.Assertions.*;

/**
 * <b>术语解释</b>：
 * <p>
 * <b>Directly Present</b>：注解直接存在于目标的字节码属性中。
 * <p>
 * <b>Indirectly Present</b>：当注解 A 被 `@Repeatable(A.Container.class)` 标记时，
 * 若容器注解 `A.Container` 直接存在且包含 A 注解，则每个 A 注解被视为间接存在。
 * <p>
 * <b>Present</b>：注解直接存在，或通过 `@Inherited` 从父类继承的直接存在的注解（仅对类有效）。
 * <p>
 * <b>Associated</b>：直接存在、间接存在的注解，以及通过继承链传递的直接或间接存在的注解。
 * <p>
 * 关系图：
 *
 * <pre>
 *  ┌──────────────────────────────────────┐
 *  │             Associated               │
 *  │  ┌─────────────────────────────────┐ │
 *  │  │            Present              │ │
 *  │  │  ┌───────────────────────────┐  │ │
 *  │  │  │       Directly Present    │  │ │
 *  │  │  └───────────────────────────┘  │ │
 *  │  │                                 │ │
 *  │  │   Inherited Directly Present    │ │
 *  │  └─────────────────────────────────┘ │
 *  │                                      │
 *  │  ┌─────────────────────────────────┐ │
 *  │  │       Indirectly Present        │ │
 *  │  └─────────────────────────────────┘ │
 *  └──────────────────────────────────────┘
 * </pre>
 *
 * <table border="1">
 *     <tr>
 *         <th>Method</th>
 *         <th>Directly Present</th>
 *         <th>Indirectly Present</th>
 *         <th>Present</th>
 *         <th>Associated</th>
 *     </tr>
 *     <tr>
 *         <td>{@code getAnnotation(Class<T>)}</td>
 *         <td>✓</td>
 *         <td>✗</td>
 *         <td>✓</td>
 *         <td>✗</td>
 *     </tr>
 *     <tr>
 *         <td>{@code getAnnotations()}</td>
 *         <td>✓</td>
 *         <td>✗</td>
 *         <td>✓</td>
 *         <td>✗</td>
 *     </tr>
 *     <tr>
 *         <td>{@code getAnnotationsByType(Class<T>)}</td>
 *         <td>✓</td>
 *         <td>✓</td>
 *         <td>✓</td>
 *         <td>✓</td>
 *     </tr>
 *     <tr>
 *         <td>{@code getDeclaredAnnotation(Class<T>)}</td>
 *         <td>✓</td>
 *         <td>✗</td>
 *         <td>✗</td>
 *         <td>✗</td>
 *     </tr>
 *     <tr>
 *         <td>{@code getDeclaredAnnotations()}</td>
 *         <td>✓</td>
 *         <td>✗</td>
 *         <td>✗</td>
 *         <td>✗</td>
 *     </tr>
 *     <tr>
 *         <td>{@code getDeclaredAnnotationsByType(Class<T>)}</td>
 *         <td>✓</td>
 *         <td>✓</td>
 *         <td>✗</td>
 *         <td>✗</td>
 *     </tr>
 * </table>
 *
 * @author yutianhong
 * @version 1.0
 * @see java.lang.reflect.AnnotatedElement
 * @since 2025/4/1 10:40
 */
public class AnnotationPresenceTest {

    @MyInheritedAnnotation
    static class Parent {
    }

    static class Child extends Parent {
    }

    @MyRepeatable("fox1")
    @MyRepeatable("fox2")
    static class Fox {
    }

    @MyNormalAnnotation("bar")
    static class Bar {
    }

    // ========== directly present ==========

    @Test
    public void testDirectlyPresent_getAnnotation() {
        MyNormalAnnotation myNormalAnnotation = Bar.class.getAnnotation(MyNormalAnnotation.class);
        assertNotNull(myNormalAnnotation);
        assertEquals("bar", myNormalAnnotation.value());
    }

    @Test
    public void testDirectlyPresent_getAnnotations() {
        Annotation[] myNormalAnnotations = Bar.class.getAnnotations();
        assertNotNull(myNormalAnnotations);
        assertEquals(1, myNormalAnnotations.length);
        assertTrue(myNormalAnnotations[0] instanceof MyNormalAnnotation);
        assertEquals("bar", ((MyNormalAnnotation) myNormalAnnotations[0]).value());
    }

    @Test
    public void testDirectlyPresent_getAnnotationsByType() {
        MyNormalAnnotation[] myNormalAnnotations = Bar.class.getAnnotationsByType(MyNormalAnnotation.class);
        assertNotNull(myNormalAnnotations);
        assertEquals(1, myNormalAnnotations.length);
        assertEquals("bar", myNormalAnnotations[0].value());
    }

    @Test
    public void testDirectlyPresent_getDeclaredAnnotation() {
        MyNormalAnnotation myNormalAnnotation = Bar.class.getDeclaredAnnotation(MyNormalAnnotation.class);
        assertNotNull(myNormalAnnotation);
        assertEquals("bar", myNormalAnnotation.value());
    }

    @Test
    public void testDirectlyPresent_getDeclaredAnnotations() {
        Annotation[] myNormalAnnotations = Bar.class.getDeclaredAnnotations();
        assertNotNull(myNormalAnnotations);
        assertEquals(1, myNormalAnnotations.length);
        assertTrue(myNormalAnnotations[0] instanceof MyNormalAnnotation);
        assertEquals("bar", ((MyNormalAnnotation) myNormalAnnotations[0]).value());
    }

    @Test
    public void testDirectlyPresent_getDeclaredAnnotationsByType() {
        MyNormalAnnotation[] myNormalAnnotations = Bar.class.getDeclaredAnnotationsByType(MyNormalAnnotation.class);
        assertNotNull(myNormalAnnotations);
        assertEquals(1, myNormalAnnotations.length);
        assertEquals("bar", myNormalAnnotations[0].value());
    }

    // ========== indirectly present ==========

    @Test
    public void testIndirectlyPresent_getAnnotation() {
        MyRepeatable ann = Fox.class.getAnnotation(MyRepeatable.class);
        assertNull(ann);
    }

    @Test
    public void testIndirectlyPresent_getAnnotations() {
        Annotation[] annotations = Fox.class.getAnnotations();
        assertEquals(1, annotations.length);
        assertTrue(annotations[0] instanceof MyRepeatableContainer);
    }

    @Test
    public void testIndirectlyPresent_getAnnotationsByType() {
        MyRepeatable[] annotations = Fox.class.getAnnotationsByType(MyRepeatable.class);
        assertEquals(2, annotations.length);
    }

    @Test
    public void testIndirectlyPresent_getDeclaredAnnotation() {
        MyRepeatable ann = Fox.class.getDeclaredAnnotation(MyRepeatable.class);
        assertNull(ann);
    }

    @Test
    public void testIndirectlyPresent_getDeclaredAnnotations() {
        Annotation[] annotations = Fox.class.getDeclaredAnnotations();
        assertEquals(1, annotations.length);
        assertTrue(annotations[0] instanceof MyRepeatableContainer);
    }

    @Test
    public void testIndirectlyPresent_getDeclaredAnnotationsByType() {
        MyRepeatable[] annotations = Fox.class.getDeclaredAnnotationsByType(MyRepeatable.class);
        assertEquals(2, annotations.length);
    }

    // ========== inherited ==========

    @Test
    public void testInherited_getAnnotation() {
        MyInheritedAnnotation ann = Child.class.getAnnotation(MyInheritedAnnotation.class);
        assertNotNull(ann);
    }

    @Test
    public void testInherited_getAnnotations() {
        Annotation[] annotations = Child.class.getAnnotations();
        boolean found = false;
        for (Annotation a : annotations) {
            if (a instanceof MyInheritedAnnotation) {
                found = true;
                break;
            }
        }
        assertTrue(found);
    }

    @Test
    public void testInherited_getAnnotationsByType() {
        MyInheritedAnnotation[] annotations = Child.class.getAnnotationsByType(MyInheritedAnnotation.class);
        assertEquals(1, annotations.length);
    }

    @Test
    public void testInherited_getDeclaredAnnotation() {
        MyInheritedAnnotation ann = Child.class.getDeclaredAnnotation(MyInheritedAnnotation.class);
        assertNull(ann);
    }

    @Test
    public void testInherited_getDeclaredAnnotations() {
        Annotation[] annotations = Child.class.getDeclaredAnnotations();
        for (Annotation a : annotations) {
            assertFalse(a instanceof MyInheritedAnnotation);
        }
    }

    @Test
    public void testInherited_getDeclaredAnnotationsByType() {
        MyInheritedAnnotation[] annotations = Child.class.getDeclaredAnnotationsByType(MyInheritedAnnotation.class);
        assertEquals(0, annotations.length);
    }

}
